package com.hanter.android.saf;

import android.Manifest;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.RecoverableSecurityException;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;

import androidx.activity.ComponentActivity;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.IntentSenderRequest;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.collection.ArrayMap;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.fragment.app.Fragment;

import com.hanter.android.saf.result.IntentSenderRequestResult;
import com.hanter.android.saf.result.StartIntentSenderForResult;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;


/**
 * App前缀：App的私有目录（内部存储+外部存储），
 * 公有目录： Environment.DIRECTORY_ 对应的目录
 * 内部存储用 File，外部存储用 Uri
 */
public class SafManagerImpl implements SafManager {

    public static final String TAG = SafManagerImpl.class.getSimpleName();

    private static final String FILE_PROVIDER_NAME = "saf.fileprovider";

    private final Context context;
    private final ContentResolver contentResolver;
    private final String authority;
    private ActivityResultLauncher<IntentSenderRequest> launcher;
    private final Map<IntentSenderRequest, DeleteCall> callbackMap = new ArrayMap<>();

    public interface OnDeleteCallback {
        void onMediaDeleteResult(boolean deleted);
    }

    public SafManagerImpl(@NonNull Context context) {
        this(context, null);
    }

    public SafManagerImpl(@NonNull Context context, @Nullable String authority) {
        this.context = context.getApplicationContext();
        this.contentResolver = context.getContentResolver();
        this.authority = authority != null ? authority :
                (this.context.getPackageName() + "." + FILE_PROVIDER_NAME);
    }

    @Override
    public void register(Fragment fragment) {
        this.launcher = fragment.registerForActivityResult(
                new StartIntentSenderForResult(),
                new ActivityResultCallback<IntentSenderRequestResult>() {
                    @Override
                    public void onActivityResult(IntentSenderRequestResult result) {
                        // 处理结果
                        DeleteCall call = callbackMap.get(result.getRequest());
                        if (call != null) {
                            call.call(result.getResultCode());
                            callbackMap.remove(result.getRequest());
                        }
                    }
                }
        );
    }

    @Override
    public void register(ComponentActivity activity) {
        this.launcher = activity.registerForActivityResult(
                new StartIntentSenderForResult(),
                new ActivityResultCallback<IntentSenderRequestResult>() {
                    @Override
                    public void onActivityResult(IntentSenderRequestResult result) {
                        // 处理结果
                        DeleteCall call = callbackMap.get(result.getRequest());
                        if (call != null) {
                            call.call(result.getResultCode());
                            callbackMap.remove(result.getRequest());
                        }
                    }
                }
        );
    }

    @Override
    public Uri getMediaContentUri(String type) {
        return getMediaContentUri(type, true);
    }

    @Override
    public Uri getMediaContentUri(String type, boolean external) {
        // Images(Pictures/DCIM)
        if (Environment.DIRECTORY_PICTURES.equals(type) || Environment.DIRECTORY_DCIM.equals(type)) {
            return external ? MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                    : MediaStore.Images.Media.INTERNAL_CONTENT_URI;
        }

        // Video(Movies)
        if (Environment.DIRECTORY_MOVIES.equals(type)) {
            return external ? MediaStore.Video.Media.EXTERNAL_CONTENT_URI
                    : MediaStore.Video.Media.INTERNAL_CONTENT_URI;
        }

        // Audio
        if (Environment.DIRECTORY_MUSIC.equals(type) ||
                Environment.DIRECTORY_PODCASTS.equals(type) ||
                Environment.DIRECTORY_RINGTONES.equals(type) ||
                Environment.DIRECTORY_ALARMS.equals(type) ||
                Environment.DIRECTORY_NOTIFICATIONS.equals(type)) {
            return external ? MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
                    : MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
        }

        // Downloads
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Environment.DIRECTORY_DOWNLOADS.equals(type)) {
            return external ? MediaStore.Downloads.EXTERNAL_CONTENT_URI
                    : MediaStore.Downloads.INTERNAL_CONTENT_URI;
        }

        if (DIRECTORY_SCREENSHOTS.equals(type)) {
            return external ? MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                    : MediaStore.Images.Media.INTERNAL_CONTENT_URI;
        }

        // Files 允许Documents和Downloads目录
        return MediaStore.Files.getContentUri(external ? "external" : "internal");
    }

    @Nullable
    @Override
    public Uri getMediaUri(@MediaType String type, String fileName) {
        String name = extractName(fileName);
        String relativePath;
        String pathColumn;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            relativePath = getRelativePath(type, fileName);
            pathColumn = MediaStore.MediaColumns.RELATIVE_PATH;
        } else {
            relativePath = getMediaFile(type, fileName).getPath();
            pathColumn = MediaStore.MediaColumns.DATA;
        }

        // Android9- 文件删除了，查询也查不到了，query应该判断了文件是否存在
        Uri contentUri = getMediaContentUri(type);
        try (Cursor cursor = contentResolver.query(contentUri,
                new String[]{
                        MediaStore.MediaColumns._ID,
                        MediaStore.MediaColumns.DISPLAY_NAME,
                        pathColumn
                },
                MediaStore.MediaColumns.DISPLAY_NAME + "=? AND " + pathColumn + "=?",
                new String[]{
                        name,
                        relativePath
                },
                null)) {

            if (cursor != null && cursor.moveToFirst()) {
                return ContentUris.withAppendedId(contentUri, cursor.getLong(0));
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public boolean exists(@Nullable Uri uri) {
        return uri != null && Utils.exists(this.context, uri);
    }

    /**
     * 判断是否存在
     * >= Q 使用 MediaStore.query 判断，但对于无后缀 MIME_TYPE 无法配置的情况下 可能无法判断
     * < Q 直接使用 File 判断是否存在
     *
     * @param type     媒体类型
     * @param fileName 文件路径名
     */
    @Override
    public boolean exists(@MediaType String type, String fileName) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            String name = extractName(fileName);
            String relativePath = getRelativePath(type, fileName);
            String selection = MediaStore.MediaColumns.DISPLAY_NAME + " = ? AND " +
                    MediaStore.MediaColumns.RELATIVE_PATH + " = ?";
            try (Cursor cursor = contentResolver.query(getMediaContentUri(type),
                    new String[]{
                            MediaStore.MediaColumns._ID,
                            MediaStore.MediaColumns.DISPLAY_NAME,
                            MediaStore.MediaColumns.RELATIVE_PATH
                    },
                    selection,
                    new String[]{
                            name,
                            relativePath
                    }, null)) {
                return cursor != null && cursor.moveToFirst();
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        } else {
            return getMediaFile(type, fileName).exists();
        }
    }

    @Override
    public Uri createMediaUri(String type, String fileName) throws IOException {
        return createMediaUri(type, fileName, (ContentValues) null);
    }

    @Override
    public Uri createMediaUri(String type, String fileName, String mimeType) throws IOException {
        ContentValues other = new ContentValues();
        other.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
        return createMediaUri(type, fileName, other);
    }

    private Uri createMediaUri(String type, String fileName, @Nullable ContentValues other) throws IOException {
        Uri uri;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            ContentValues contentValues = new ContentValues();
            String name = extractName(fileName);
            String relativePath = getRelativePath(type, fileName);
            contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
            if (other != null)
                contentValues.putAll(other);
            // 参考：https://developer.android.com/reference/android/provider/MediaStore.MediaColumns#RELATIVE_PATH
            // android Q中不再使用DATA字段，而用RELATIVE_PATH代替，RELATIVE_PATH是相对路径不是绝对路径
            // 如果文件 /storage/0000-0000/DCIM/Vacation/IMG1024.JPG 则它的路径为 DCIM/Vacation/
            // 可以传空，但会根据媒体类型自动选择目录；如果你填了 audio/mpeg 到 PICTURES 会报错，类型相关目录
            // 根据 type 选择 路径
            contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath);
            Log.d(TAG, "media file: " + relativePath + name);
            uri = context.getContentResolver().insert(
                    getMediaContentUri(type, existsExternalStorage()),
                    contentValues
            );

            if (uri != null) {
                try (Cursor cursor = context.getContentResolver().query(
                        uri,
                        new String[]{
                                MediaStore.MediaColumns.DISPLAY_NAME,
                                MediaStore.MediaColumns.RELATIVE_PATH
                        },
                        null,
                        null,
                        null
                )) {
                    if (cursor != null && cursor.moveToFirst()) {
                        String resultName = cursor.getString(0);
                        String resultPath = cursor.getString(1);
                        if (!TextUtils.equals(name, resultName)
                                || !TextUtils.equals(relativePath, resultPath)) {
                            Log.d(TAG, "插入失败，已存在");
                            delete(uri);
                            uri = null;
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } else {
            if (!existsExternalStorage())
                throw new IOException("创建失败，外部存储可不用！");

            // 使用公共存储目录，还是使用 App 存储目录？
            if (hasExternalPermission()) {
                // 低于 Q 手动创建，然后授权 读写
                File mediaFile = getMediaFile(type, fileName);
                createFile(mediaFile);
                ContentValues contentValues = new ContentValues();
                contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, mediaFile.getName());
                if (other != null)
                    contentValues.putAll(other);
                contentValues.put(MediaStore.MediaColumns.DATA, mediaFile.getPath());

                // 如果已存在记录，则会失败返回 null
                uri = context.getContentResolver().insert(
                        getMediaContentUri(type, existsExternalStorage()),
                        contentValues
                );
            } else {
                // 对于没有存储权限，只能在 context.getExternalFilesDir 目录创建（不需要存储权限）
                File mediaFile = getMediaFile(type, fileName);
                createFile(mediaFile);
                uri = FileProvider.getUriForFile(this.context, this.authority, mediaFile);
            }
        }

        // 目标存储的URI
        if (uri != null)
            Log.d(TAG, "create mediaUri successful: " + uri);
        else
            Log.d(TAG, "create mediaUri failed. type: " + type + ", file: " + fileName);
        return uri;
    }

    @Override
    public Uri createImageUri(String fileName) throws IOException {
        return createImageUri(fileName, null);
    }

    @Override
    public Uri createImageUri(String fileName, String mimeType) throws IOException {
        ContentValues other = new ContentValues();
        other.put(MediaStore.MediaColumns.MIME_TYPE, mimeType != null ? mimeType : "image/jpeg");
        return createMediaUri(Environment.DIRECTORY_PICTURES, fileName, other);
    }

    @Override
    public boolean delete(@NonNull Uri uri) {
        try {
            return deleteInternal(uri);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private boolean deleteInternal(@NonNull Uri uri) {
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
            return deleteInternal(uri, Utils.getPath(context, uri));
        } else {
            return deleteInternal(uri, null);
        }
    }

    private boolean deleteInternal(@NonNull Uri uri, @Nullable String path) {
        // Android 11+ ContentResolver.delete 时会顺便删除文件；
        // Android 10- 无论是否开启 分区存储，都不会删除对应文件，仅仅删除了记录
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
            return contentResolver.delete(uri, null, null) > 0;
        } else {
            // 这里可能删除未成功，因为没有 WRITE_EXTERNAL_STORAGE 权限，导致行为错误
            File file = path != null ? new File(path) : null;
            if (file != null && file.exists()) {
                return file.delete() && contentResolver.delete(uri, null, null) > 0;
            } else {
                return contentResolver.delete(uri, null, null) > 0;
            }
        }
    }

    private static class DeleteCall {

        private final SafManagerImpl safManager;
        private final Uri uri;
        private final OnDeleteCallback callback;

        public DeleteCall(@NonNull SafManagerImpl safManager, @NonNull Uri uri, @NonNull OnDeleteCallback callback) {
            this.safManager = safManager;
            this.uri = uri;
            this.callback = callback;
        }

        public void call(int resultCode) {
            if (resultCode == Activity.RESULT_OK) {
                try {
                    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
                        this.callback.onMediaDeleteResult(true);
                    } else {
                        this.callback.onMediaDeleteResult(safManager.delete(uri));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    this.callback.onMediaDeleteResult(false);
                }
            } else {
                this.callback.onMediaDeleteResult(false);
            }
        }

    }

    @Override
    public void delete(@NonNull Uri uri, @NonNull OnDeleteCallback callback) {
        try {
            callback.onMediaDeleteResult(deleteInternal(uri));
        } catch (Exception e) {
            e.printStackTrace();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && e instanceof RecoverableSecurityException) {
                RecoverableSecurityException rse = (RecoverableSecurityException) e;
                PendingIntent pendingIntent;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                    // MediaStore.createDeleteRequest 可以避免显示弹窗，要求权限：READ_EXTERNAL_STORAGE
                    pendingIntent = MediaStore.createDeleteRequest(contentResolver,
                            Collections.singletonList(uri));
                } else {
                    pendingIntent = rse.getUserAction().getActionIntent();
                }
                if (pendingIntent != null) {
                    IntentSenderRequest isr = new IntentSenderRequest.Builder(pendingIntent)
                            .build();
                    callbackMap.put(isr, new DeleteCall(this, uri, callback));
                    launcher.launch(isr);
                } else {
                    callback.onMediaDeleteResult(false);
                }
            } else if (e instanceof SecurityException) {
                e.printStackTrace();
                // 没有 WRITE_EXTERNAL_STORAGE 权限
                callback.onMediaDeleteResult(false);
            } else {
                callback.onMediaDeleteResult(false);
            }
        }
    }

    @Override
    public boolean delete(@NonNull Uri contentUri, long rowId) {
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
            //noinspection deprecation
            try (Cursor cursor = contentResolver.query(contentUri,
                    new String[]{
                            MediaStore.MediaColumns._ID,
                            MediaStore.MediaColumns.DATA
                    },
                    MediaStore.MediaColumns._ID + "= ?",
                    new String[]{
                            String.valueOf(rowId)
                    },
                    null)) {

                if (cursor != null && cursor.moveToFirst()) {
                    // 查询第一条
                    Uri uri = Uri.withAppendedPath(contentUri, String.valueOf(rowId));
                    return deleteInternal(uri, cursor.getString(1));
                } else {
                    return false;
                }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        } else {
            return deleteInternal(Uri.withAppendedPath(contentUri, String.valueOf(rowId)));
        }
    }

    @Override
    public File getAppMediaDirectory(String type, boolean external) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (hasExternalPermission() && existsExternalStorage()) {
                return this.context.getExternalFilesDir(type);
            } else {
                return new File(this.context.getFilesDir(), type);
            }
        } else if (existsExternalStorage()) {
            return this.context.getExternalFilesDir(type);
        } else {
            return new File(this.context.getFilesDir(), type);
        }
    }

    @Override
    public File getAppFilesDirectory(boolean external) {
        if (external && existsExternalStorage()) {
            return this.context.getExternalFilesDir(null);
        } else {
            return this.context.getFilesDir();
        }
    }

    private boolean existsExternalStorage() {
        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                && !Environment.isExternalStorageRemovable();
    }

    /**
     * 外部存储是否可写入（外部写入权限）
     */
    private boolean isExternalWritable() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return this.context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
                    PackageManager.PERMISSION_GRANTED && existsExternalStorage();
        } else {
            return existsExternalStorage();
        }
    }

    private boolean hasExternalPermission() {
        return ContextCompat.checkSelfPermission(this.context,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
    }

    private File getMediaDirectory(String type) {
        if (isExternalWritable()) {
            return getPublicMediaDirectory(type);
        } else {
            return new File(getAppFilesDirectory(existsExternalStorage()), type);
        }
    }

    /**
     * 获取外部公共存储的目录
     */
    private File getPublicMediaDirectory(String type) {
        // 根据类型调整目录，达到 < Q 和 Q 目录统一性

        if (DIRECTORY_AUDIOBOOKS.equals(type)) {
            return new File(
                    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),
                    DIRECTORY_AUDIOBOOKS
            );
        }

        if (DIRECTORY_SCREENSHOTS.equals(type)) {
            return new File(
                    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                    DIRECTORY_SCREENSHOTS
            );
        }

        return Environment.getExternalStoragePublicDirectory(type);
    }

    /**
     * 获取路径后的文件名
     */
    @NonNull
    private String extractName(String fileName) {
        String name;
        int sepEnd = fileName.lastIndexOf(File.separator);
        if (sepEnd == -1) {
            name = fileName;
        } else {
            name = fileName.substring(sepEnd + 1);
        }
        return name;
    }

    private String getPublicDirectoryPath(String type) {
        // Audiobooks allowed directories are [Download, Documents]

        if (DIRECTORY_AUDIOBOOKS.equals(type)) {
            return Environment.DIRECTORY_DOCUMENTS + File.separator + DIRECTORY_AUDIOBOOKS;
        } else if (DIRECTORY_SCREENSHOTS.equals(type)) {
            return Environment.DIRECTORY_PICTURES + File.separator + DIRECTORY_SCREENSHOTS;
        }

        return type;
    }

    /**
     * 返回目录路径，例如 Music/
     *
     * @param type     媒体类型
     * @param fileName 文件路径名
     */
    @RequiresApi(api = Build.VERSION_CODES.Q)
    private String getRelativePath(String type, String fileName) {
        int sepEnd = fileName.lastIndexOf(File.separator);
        if (sepEnd == -1) {
            return getPublicDirectoryPath(type) + File.separator;
        } else {
            // 包含分隔符
            String path = fileName.substring(0, sepEnd + 1);
            return getPublicDirectoryPath(type) + File.separator + path;
        }
    }

    /**
     * 获取媒体文件，但并未创建。< Android Q 时用到
     * Api < Build.VERSION_CODES.Q
     *
     * @param type     媒体类型
     * @param fileName 文件路径名
     */
    public File getMediaFile(String type, String fileName) {
        File file = new File(getMediaDirectory(type), fileName);
        Log.d(TAG, "media file: " + file.getAbsolutePath());
        return file;
    }

    private void createFile(File file) throws IOException {
        // 创建文件
        File parent = file.getParentFile();
        if (parent != null && !parent.exists())
            //noinspection ResultOfMethodCallIgnored
            parent.mkdirs();
        if (!file.exists())
            //noinspection ResultOfMethodCallIgnored
            file.createNewFile();
    }
}